package newcrunch.codeanalysis;

import com.puppycrawl.tools.checkstyle.*;
import com.puppycrawl.tools.checkstyle.api.AuditListener;
import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
import com.puppycrawl.tools.checkstyle.api.Configuration;
import java.io.*;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Properties;
import javax.swing.JEditorPane;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import newcrunch.gui.RightTab;
import newcrunch.util.Logging;
import newcrunch.util.Misc;
import newcrunch.util.PropertyLoader;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

/**
 *
 * @author ccadm
 */
public class CheckStylePlugin 
{
    private static String configFile = "/newcrunch/codeanalysis/checkstyle.xml";
    private static DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance();
    private static DocumentBuilder docBuilder;
    
    static
    {
        try {
            configFile = PropertyLoader.getRulesProperty("checkstyle");
            docBuilder = docBuilderFactory.newDocumentBuilder();
        } catch (Exception exp) {
            Logging.log(exp);
        }
    }    
    
    private CheckStylePlugin() {}
    
    /*
     * Test to check that it works.
     */
//    public static void main(String[] args) throws FileNotFoundException, IOException
//    {
//        String directory = "/home/ccadm/NetBeansProjects/NewCrunch/src/main/java/newcrunch/";
//        String output = "/home/ccadm/Desktop/output1.txt";
//        String format = "plain";
//        String configFile = "/newcrunch/codeanalysis/checkstyle.xml";
//        List<File> files = new ArrayList<File>(); 
//        
//        File root = new File(directory);
//        files.add(root);
//        int index = 0;
//        File curr = null;
//        while (index < files.size())
//        {
//            curr = files.get(index);
//            if (!".".equals(curr.getName()) && !"..".equals(curr.getName()) && 
//                    curr.isDirectory())
//                Collections.addAll(files, curr.listFiles());
//            index++;
//        }
//        
//        File outputFile = new File(output);
//        
//        if (outputFile.exists())
//            outputFile.createNewFile();
//        
//        FileOutputStream fout = new FileOutputStream(outputFile);
        
//        for (File file : files)
//        {
//            if (file.isDirectory())
//                continue;
            
//            try {
//                String filename = "/home/ccadm/Desktop/HelloWorld.java";
//                FileOutputStream fout = new FileOutputStream(new File(output));
//                File file = new File(filename);
//                ByteArrayOutputStream bout = new ByteArrayOutputStream();
//                int result = CheckStylePlugin.checkFiles(bout, format, configFile, file);
//                byte[] res = bout.toByteArray();
//                fout.write(res, 0, res.length);
//                System.out.println(file.getName() + " : " + result);
//            } catch (Exception e) {
//                e.printStackTrace();
//            }
            
//        }
        
//    }
    
    public static InfoNode runCheckStyle(RightTab rightTab)
    {
        try {
            JEditorPane editor = rightTab.getEditor();
            String output = getCheckstyleOutput(rightTab.getText()).trim();
            
            InfoNode node = new InfoNode("Checkstyle");
            HashMap<String, InfoNode> grouping = new HashMap<String, InfoNode>();
            
            Document doc = docBuilder.parse(new ByteArrayInputStream(output.getBytes()));
        
            Element root = doc.getDocumentElement();
            
            Element fileNode = PluginUtil.getFirstElement(root);
            
            NodeList list = fileNode.getChildNodes();
            
            for (int i = 0; i < list.getLength(); i++)
            {
                if (list.item(i) instanceof Element)
                {
                    Element currElement = (Element)list.item(i);
                    if ("error".equals(currElement.getTagName()))
                    {
                        int row = 1;
                        int col = 1;
                        String rowStr = "";
                        String colStr = "";
                       
                        if (!"".equals(rowStr = currElement.getAttribute("line")))
                           row = Integer.parseInt(rowStr);
                       
                       if (!"".equals(colStr = currElement.getAttribute("column")))
                           col = Integer.parseInt(colStr);
                                              
                       String source = currElement.getAttribute("source");
                       String header = source.substring(source.lastIndexOf(".") + 1);
                       
                       InfoNode headerNode = null;
                       if ((headerNode = grouping.get(header)) == null)
                       {
                           headerNode = new InfoNode(header);
                           grouping.put(header, headerNode);
                       }
                       
                       int startPos = Misc.rowToPosition(editor, row - 1);
                       startPos = startPos + PluginUtil.dispIndexToStrIndex(editor.getText().substring(startPos), col - 1);
                       headerNode.add(new InfoNode(currElement.getAttribute("message"), startPos, startPos));
                    }
                }
            }
            
            String[] headers = grouping.keySet().toArray(new String[]{});
            Arrays.sort(headers);
            for (String header : headers)
                node.add(grouping.get(header));
            
            return node;
        } catch (IOException exp) { 
            Logging.log(exp);
            return null;
        } catch (SAXException exp) { 
            Logging.log(exp);
            return null;
        } catch (NumberFormatException exp) { 
            Logging.log(exp);
            return null;
        }
    }
    
    private static String getCheckstyleOutput(String code) throws IOException
    {
        ByteArrayOutputStream bout = new ByteArrayOutputStream();
        File temp = createTempCodeFile("checkstyle", code);

        CheckStylePlugin.checkFiles(bout, "xml", configFile, temp);
        
        temp.delete();        
        return bout.toString();
    }    
    
    private static File createTempCodeFile(String prefix, String code) throws IOException
    {
        File temp = File.createTempFile(prefix, ".java");
        temp.setWritable(true);
        temp.setReadable(true);
        temp.setExecutable(true);
        
        FileWriter writer = new FileWriter(temp);
        writer.append(code);
        writer.close();
        
        return temp;
    }    
    
    /**
     * Main entry point to perform file checks using Checkstyle.
     * @param out - OutputStream to print the results
     * @param format - XML / Plain
     * @param configFile - File containing the checks to perform
     * @param files - Files to perform checks on
     * @return - the number of errors encountered while processing the files,
     * or -1 if exceptions were encountered before file checking can be performed.
     */
    public static int checkFiles(OutputStream out, String format, String configFile, File... files)
    {
        try {
            AuditListener listener = createListener(out, format);
            Configuration config = loadConfig(configFile, System.getProperties());
            Checker checker = createChecker(config, listener);
            return checker.process(Arrays.asList(files));
        } catch (CheckstyleException exp) {
            Logging.log(exp);
            return -1;
        }
    }
    
    /**
     * Creates the Checker object.
     *
     * @param aConfig the configuration to use
     * @param aNosy the sticky beak to track what happens
     * @return a nice new fresh Checker
     */    
    private static Checker createChecker(Configuration aConfig,
                                         AuditListener aNosy) throws CheckstyleException
    {
        Checker checker = new Checker();

        final ClassLoader moduleClassLoader =
            Checker.class.getClassLoader();
        checker.setModuleClassLoader(moduleClassLoader);
        checker.configure(aConfig);
        checker.addListener(aNosy);
        
        return checker;
    }
    
    /**
     * Loads the configuration file.
     * @param configFilename
     * @param aProps
     * @return
     * @throws CheckstyleException 
     */
    private static Configuration loadConfig(String configFilename,
                                            Properties aProps) throws CheckstyleException
    {
            return ConfigurationLoader.loadConfiguration(
                    configFilename, new PropertiesExpander(aProps));
    }

    /**
     * Creates an audit listener
     * @param out
     * @param format
     * @return 
     */
    private static AuditListener createListener(OutputStream out, String format)
    {
        AuditListener listener = null;
        boolean aCloseOut = true;
        
        if ("xml".equals(format)) {
            listener = new XMLLogger(out, aCloseOut);
        }
        else if ("plain".equals(format)) {
            listener = new DefaultLogger(out, aCloseOut);
        }
        else {
            throw new IllegalArgumentException("Invalid format: (" + format
                                + "). Must be 'plain' or 'xml'.");
        }        
        return listener;
    }
}
